📖

请从左侧选择一篇笔记开始阅读

\n\n\n\n```\n\n---\n\n## 五、requestIdleCallback vs requestAnimationFrame\n\n| API | 时机 | 用途 |\n|-----|------|------|\n| `requestAnimationFrame` | 每帧渲染**之前** | 动画、DOM 更新 |\n| `requestIdleCallback` | 浏览器**空闲**时 | 低优先级任务(上报、预加载) |\n\n---\n\n## 六、高频面试题\n\n### Q1:为什么 CSS 要放 head,JS 要放 body 底部?\n\n> CSS 是渲染阻塞资源,放 head 让页面尽早完成 CSSOM 构建。JS 默认会阻塞 HTML 解析(script 标签会暂停 DOM 构建),放底部减少白屏时间。\n\n### Q2:display:none 的元素在渲染树中吗?\n> 不在。`display:none` 不生成盒子,不在渲染树中。但 `visibility:hidden` 在渲染树中。\n\n### Q3:哪些操作会触发回流?\n> 增删 DOM、修改元素尺寸/位置、`offsetWidth/offsetHeight` 等读取也会**强制同步布局**。\n\n### Q4:为什么读 offsetWidth 会触发回流?\n> 读取布局属性时,浏览器必须保障值是最新的,若有未应用的样式变更,会先强制回流再计算,这是\"强制同步布局\"(FSL)。\n\n---\n\n*整理时间:2026-06-03*\n", "08-HTTP": "# HTTP 面试笔记\n\n---\n\n## 一、HTTP 是什么?\n\n**超文本传输协议**(HyperText Transfer Protocol),用于客户端和服务器之间的通信。\n\n---\n\n## 二、HTTP 版本对比\n\n| 特性 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 |\n|------|----------|----------|--------|--------|\n| 连接方式 | 短连接 | 持久连接 | 多路复用 | 多路复用(基于 QUIC) |\n| 队头阻塞 | 有 | 有 | 解决 | 解决 |\n| 头部压缩 | ❌ | ❌ | HPACK | QPACK |\n| 服务器推送 | ❌ | ❌ | ✅ | ✅ |\n| 传输协议 | TCP | TCP | TCP | UDP(QUIC) |\n| 推出年份 | 1996 | 1999 | 2015 | 2022 |\n\n---\n\n## 三、HTTP 1.1 vs HTTP 2.0\n\n### HTTP/1.1 的问题\n- **队头阻塞**:单个 TCP 连接上请求必须排队\n- **头部冗余**:每次请求都带完整头部\n\n### HTTP/2 的改进\n\n```yaml\n多路复用: 一个连接并发多个请求/响应\n二进制分帧: 报文切割为二进制帧,交错传输\n头部压缩 (HPACK): 索引表压缩重复头部\n服务器推送: 服务端主动推送资源\n```\n\n---\n\n## 四、HTTPS = HTTP + TLS/SSL\n\n### 加密过程\n\n```\n客户端 → Server Hello → 服务端\n ↓\n 证书验证 + 预主密钥加密\n ↓\n 双方生成会话密钥(对称加密)\n ↓\n 开始加密通信\n```\n\n### 三种 HTTP\n\n| 方式 | 原理 | 特点 |\n|------|------|------|\n| HTTP | 明文传输 | 不安全 |\n| HTTPS | HTTP + TLS 加密 | 安全,有证书 |\n| HTTP/3 | QUIC(UDP)+ TLS 1.3 | 0-RTT 建连,低延迟 |\n\n---\n\n## 五、HTTP 状态码\n\n| 范围 | 含义 | 常用 |\n|------|------|------|\n| 1xx | 信息 | 101 协议切换(WebSocket) |\n| 2xx | 成功 | 200 OK,201 Created,204 No Content |\n| 3xx | 重定向 | 301 永久,302 临时,304 未修改 |\n| 4xx | 客户端错误 | 400 Bad Request,401 未授权,403 禁止,404 不存在 |\n| 5xx | 服务端错误 | 500 服务器错误,502 网关错,503 服务不可用 |\n\n---\n\n## 六、请求方法\n\n| 方法 | 用途 | 幂等 | 安全 |\n|------|------|------|------|\n| GET | 获取资源 | ✅ | ✅ |\n| POST | 创建资源 | ❌ | ❌ |\n| PUT | 完整更新 | ✅ | ❌ |\n| PATCH | 部分更新 | ❌ | ❌ |\n| DELETE | 删除资源 | ✅ | ❌ |\n| HEAD | 获取头部 | ✅ | ✅ |\n| OPTIONS | 预检请求 | ✅ | ✅ |\n\n---\n\n## 七、GET vs POST\n\n| 对比 | GET | POST |\n|------|-----|------|\n| 参数位置 | URL 查询串 | 请求体 |\n| 长度限制 | 约 2KB(浏览器限制) | 无 |\n| 缓存 | 可缓存 | 不缓存 |\n| 书签 | 可收藏 | 不可收藏 |\n| 安全性 | URL 明文 | 相对安全(HTTPS 都加密) |\n\n---\n\n## 八、Cookie / Session / Token / JWT\n\n| 技术 | 存哪 | 特点 |\n|------|------|------|\n| Cookie | 浏览器 | 自动带在请求头,有大小限制(4KB) |\n| Session | 服务端 | 基于 Cookie 的 SessionID,服务端存储 |\n| Token | 客户端 | 无状态,服务端只需验证签名 |\n| JWT | 客户端 | Header.Payload.Signature 三段式,自包含 |\n\n---\n\n## 九、强缓存 & 协商缓存\n\n```yaml\n强缓存(不请求服务器):\n - Expires(绝对时间,HTTP/1.0)\n - Cache-Control: max-age=3600(相对时间,优先级高)\n\n协商缓存(问服务器):\n - Last-Modified / If-Modified-Since(秒级精度)\n - ETag / If-None-Match(精确,优先级高)\n```\n\n---\n\n## 十、高频面试题\n\n### Q1:HTTP/2 如何解决队头阻塞?\n> 通过**多路复用**,在一个 TCP 连接上并发多个 stream,每个 stream 独立传输,不互相阻塞。但 TCP 层面的队头阻塞仍存在(HTTP/3 用 QUIC 解决)。\n\n### Q2:从输入 URL 到页面展示发生了什么?\n> DNS 解析 → TCP 三次握手 → TLS 握手(HTTPS) → 发送 HTTP 请求 → 服务器处理返回 → 浏览器解析 HTML → 构建 DOM/CSSOM → 渲染 → 显示\n\n### Q3:304 和 200 缓存有什么区别?\n> 304 表示资源未修改,浏览器用本地缓存,不传响应体;200 返回完整内容。304 状态必须结合协商缓存(ETag/Last-Modified)使用。\n\n### Q4:TCP 三次握手过程?\n> SYN → SYN+ACK → ACK。目的:确认双方收发能力正常,同步初始序列号。\n\n### Q5:JWT 的结构是什么?\n> `Header.Payload.Signature`。前两部分 Base64 编码,第三部分用 secret 签名。可自包含用户信息,但不可存放敏感数据(前两部分可解码)。\n\n---\n\n*整理时间:2026-06-03*\n", "09-算法": "# 前端高频算法面试笔记\n\n---\n\n## 一、排序算法\n\n### 1. 快排(Quick Sort)\n\n```js\nfunction quickSort(arr) {\n if (arr.length <= 1) return arr\n const pivot = arr[0]\n const left = arr.slice(1).filter(x => x < pivot)\n const right = arr.slice(1).filter(x => x >= pivot)\n return [...quickSort(left), pivot, ...quickSort(right)]\n}\n// 时间 O(nlogn),空间 O(nlogn),不稳定\n```\n\n### 2. 归并排序(Merge Sort)\n\n```js\nfunction mergeSort(arr) {\n if (arr.length <= 1) return arr\n const mid = Math.floor(arr.length / 2)\n const left = mergeSort(arr.slice(0, mid))\n const right = mergeSort(arr.slice(mid))\n return merge(left, right)\n}\nfunction merge(a, b) {\n const res = []\n while (a.length && b.length) res.push(a[0] < b[0] ? a.shift() : b.shift())\n return [...res, ...a, ...b]\n}\n// 时间 O(nlogn),空间 O(n),稳定\n```\n\n---\n\n## 二、数组去重\n\n```js\n// 方式1:Set(推荐)\n[...new Set(arr)]\n\n// 方式2:filter + indexOf\narr.filter((v, i) => arr.indexOf(v) === i)\n\n// 方式3:对象数组按字段去重\nconst dedup = (arr, key) => {\n const seen = new Map()\n return arr.filter(v => !seen.has(v[key]) && seen.set(v[key], 1))\n}\n```\n\n---\n\n## 三、扁平化数组\n\n```js\n// 方式1:flat(ES2019)\nconst flat = arr.flat(Infinity)\n\n// 方式2:递归\nfunction flatten(arr) {\n return arr.reduce((acc, val) =>\n acc.concat(Array.isArray(val) ? flatten(val) : val), []\n )\n}\n\n// 方式3:栈循环(非递归)\nfunction flatten(arr) {\n const stack = [...arr], res = []\n while (stack.length) {\n const cur = stack.pop()\n Array.isArray(cur) ? stack.push(...cur) : res.unshift(cur)\n }\n return res\n}\n```\n\n---\n\n## 四、防抖 & 节流\n\n### 防抖(Debounce)\n\n```js\nfunction debounce(fn, delay) {\n let timer = null\n return function(...args) {\n clearTimeout(timer)\n timer = setTimeout(() => fn.apply(this, args), delay)\n }\n}\n// 场景:搜索输入、窗口resize\n```\n\n### 节流(Throttle)\n\n```js\nfunction throttle(fn, delay) {\n let last = 0\n return function(...args) {\n const now = Date.now()\n if (now - last >= delay) {\n last = now\n fn.apply(this, args)\n }\n }\n}\n// 场景:滚动、鼠标移动\n```\n\n---\n\n## 五、深浅拷贝\n\n```js\n// 浅拷贝\nconst copy = { ...obj }\nconst copy = Object.assign({}, obj)\n\n// 深拷贝(基础版)\nfunction deepClone(obj, map = new WeakMap()) {\n if (typeof obj !== 'object' || obj === null) return obj\n if (map.has(obj)) return map.get(obj) // 循环引用\n\n const result = Array.isArray(obj) ? [] : {}\n map.set(obj, result)\n for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n result[key] = deepClone(obj[key], map)\n }\n }\n return result\n}\n\n// 一行深拷贝(无函数/循环引用场景)\nconst copy = JSON.parse(JSON.stringify(obj))\n// 缺陷:丢失 undefined、函数、Symbol、循环引用\n```\n\n---\n\n## 六、数组相关\n\n### 两数之和\n\n```js\nfunction twoSum(nums, target) {\n const map = new Map()\n for (let i = 0; i < nums.length; i++) {\n const diff = target - nums[i]\n if (map.has(diff)) return [map.get(diff), i]\n map.set(nums[i], i)\n }\n}\n```\n\n### 无重复字符的最长子串\n\n```js\nfunction lengthOfLongestSubstring(s) {\n let l = 0, max = 0\n const set = new Set()\n for (let r = 0; r < s.length; r++) {\n while (set.has(s[r])) set.delete(s[l++])\n set.add(s[r])\n max = Math.max(max, r - l + 1)\n }\n return max\n}\n```\n\n---\n\n## 七、复杂度速查表\n\n| 算法 | 时间(平均) | 空间 | 稳定 |\n|------|-------------|------|------|\n| 快排 | O(nlogn) | O(logn) | ❌ |\n| 归并 | O(nlogn) | O(n) | ✅ |\n| 冒泡 | O(n²) | O(1) | ✅ |\n| 插入 | O(n²) | O(1) | ✅ |\n| 二分查找 | O(logn) | O(1) | - |\n| 哈希查找 | O(1) | O(n) | - |\n\n---\n\n## 八、高频面试题\n\n### Q1:防抖和节流的区别?\n> 防抖:连续触发只执行**最后一次**(等停止触发后延迟执行)。节流:固定时间间隔**只执行一次**(稀释执行频率)。\n\n### Q2:什么是稳定排序?\n> 相等元素的相对顺序在排序后保持不变。\n\n### Q3:深拷贝如何处理循环引用?\n> 使用 `WeakMap` 缓存已拷贝对象,遇到循环引用直接返回缓存的引用。\n\n### Q4:为什么用 WeakMap 而不是 Map 做缓存?\n> WeakMap 的键是弱引用,不影响 GC。如果用 Map,被拷贝对象即使外部已无引用也无法被回收。\n\n---\n\n*整理时间:2026-06-03*\n", "10-CSS布局": "# CSS 布局面试笔记\n\n---\n\n## 一、盒模型\n\n```\n┌─────────────────────────────────┐\n│ margin │\n│ ┌─────────────────────────┐ │\n│ │ border │ │\n│ │ ┌─────────────────┐ │ │\n│ │ │ padding │ │ │\n│ │ │ ┌────────────┐ │ │ │\n│ │ │ │ content │ │ │ │\n│ │ │ └────────────┘ │ │ │\n│ │ └─────────────────┘ │ │\n│ └─────────────────────────┘ │\n└─────────────────────────────────┘\n```\n\n### content-box vs border-box\n\n```css\n/* 标准盒模型:width = content */\nbox-sizing: content-box; /* 默认 */\n\n/* IE 盒模型:width = content + padding + border */\nbox-sizing: border-box; /* 推荐 */\n```\n\n---\n\n## 二、BFC(块级格式化上下文)\n\n### 是什么?\n\nBFC 是一个**独立渲染区域**,内部元素的布局不会影响外部。\n\n### 触发条件\n\n```css\noverflow: hidden / auto / scroll\ndisplay: flex / inline-flex / grid\nposition: absolute / fixed\nfloat: left / right\n```\n\n### 解决的问题\n\n| 问题 | BFC 方案 |\n|------|----------|\n| 外边距折叠 | 父元素触发 BFC |\n| 清除浮动 | 父元素触发 BFC(overflow: hidden) |\n| 两栏布局 | 浮动元素 + BFC 兄弟元素 |\n\n```css\n/* 解决外边距折叠 */\n.parent { overflow: hidden }\n\n/* 清除浮动 */\n.clearfix::after {\n content: '';\n display: block;\n clear: both;\n}\n```\n\n---\n\n## 三、居中方案\n\n### 水平居中\n\n```css\n/* 块级 */\n.element { margin: 0 auto }\n\n/* 行内/文本 */\n.parent { text-align: center }\n\n/* flex */\n.parent { display: flex; justify-content: center }\n```\n\n### 垂直居中\n\n```css\n/* 单行文本 */\n.element { line-height: 等于height }\n\n/* flex */\n.parent { display: flex; align-items: center }\n\n/* grid */\n.parent { display: grid; place-items: center }\n```\n\n### 水平垂直居中\n\n```css\n/* flex(推荐) */\n.parent {\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n/* 绝对定位 + transform(未知宽高) */\n.child {\n position: absolute; top: 50%; left: 50%;\n transform: translate(-50%, -50%);\n}\n\n/* grid(最简洁) */\n.parent { display: grid; place-items: center }\n```\n\n---\n\n## 四、Flex 布局\n\n```css\n.container {\n display: flex;\n /* 主轴方向 */\n flex-direction: row | column;\n /* 主轴对齐 */\n justify-content: flex-start | center | space-between | space-around;\n /* 交叉轴对齐 */\n align-items: stretch | center | flex-start | flex-end;\n /* 换行 */\n flex-wrap: nowrap | wrap;\n}\n\n.item {\n flex: 1; /* flex-grow flex-shrink flex-basis 缩写 */\n flex-grow: 1; /* 放大比例 */\n flex-shrink: 0; /* 缩小比例 */\n flex-basis: 200px; /* 基准大小 */\n}\n```\n\n### 经典:两栏/三栏布局\n\n```css\n/* 两栏:左侧固定,右侧自适应 */\n.container { display: flex }\n.left { width: 200px }\n.right { flex: 1 }\n\n/* 三栏:圣杯/双飞翼 = flex 头尾固定 + 中间自适应 */\n.container { display: flex }\n.left, .right { width: 200px }\n.center { flex: 1 }\n```\n\n---\n\n## 五、Grid 布局\n\n```css\n.container {\n display: grid;\n grid-template-columns: 200px 1fr 1fr; /* 列定义 */\n grid-template-rows: auto 1fr;\n gap: 16px; /* 间距 */\n grid-template-areas:\n \"header header header\"\n \"sidebar main main\";\n}\n```\n\n---\n\n## 六、CSS 选择器优先级\n\n```\n!important > 内联 > #id > .class > 标签 > 通配符\n\n具体计算 (a, b, c):\n a = 内联样式\n b = ID 选择器数量\n c = class/属性/伪类 数量\n d = 标签/伪元素 数量\n\n/* (0,1,2,1) */\n#app .list li.active { ... }\n```\n\n---\n\n## 七、高频面试题\n\n### Q1:为什么推荐 border-box?\n> 设置宽高时 padding/border 包含在内,布局计算更直观。比如 `width: 200px` 始终是 200px,不用担心 padding 撑开容器。\n\n### Q2:隐藏元素有哪些方式?\n\n| 方式 | 占空间 | 事件是否响应 |\n|------|--------|-------------|\n| `display: none` | ❌ | ❌ |\n| `visibility: hidden` | ✅ | ❌ |\n| `opacity: 0` | ✅ | ✅ |\n| `position: absolute; left: -9999px` | ❌ | ❌ |\n\n### Q3:em / rem / vw / vh 的区别?\n\n| 单位 | 相对谁 |\n|------|--------|\n| em | 父元素字体大小 |\n| rem | 根元素(html)字体大小 |\n| vw / vh | 视口宽度/高度的 1% |\n\n---\n\n*整理时间:2026-06-03*\n", "11-性能优化": "# 性能优化面试笔记\n\n---\n\n## 一、性能指标(Core Web Vitals)\n\n| 指标 | 衡量 | 目标 |\n|------|------|------|\n| **LCP**(最大内容绘制) | 加载速度 | < 2.5s |\n| **FID**(首次输入延迟) | 交互性 | < 100ms |\n| **CLS**(累计布局偏移) | 视觉稳定性 | < 0.1 |\n\n---\n\n## 二、加载优化\n\n### 1. 资源压缩\n\n```yaml\n代码压缩: webpack/vite 的 terser/esbuild\n图片压缩: webp/avif 格式替换 png/jpg\nGzip/Brotli: 服务端开启压缩\nTree Shaking: 消除未使用的代码(ESM 天然支持)\n```\n\n### 2. 懒加载\n\n```html\n\n\n\n\nconst Home = () => import('@/views/Home.vue')\n\n\n import('./Heavy.vue')\" />\n```\n\n### 3. 预加载\n\n```html\n\n\n\n\n\n\n\n\n\n\n\n```\n\n---\n\n## 三、渲染优化\n\n### 1. 减少回流/重绘(见 07-浏览器渲染.md)\n\n### 2. 虚拟列表\n\n```html\n\n\n```\n\n### 3. CSS 优化\n\n```css\n/* ❌ 后代选择器性能差 */\n.container ul li a { }\n\n/* ✅ 直接类选择器 */\n.nav-link { }\n\n/* ✅ transform 做动画(不走 layout/paint) */\n.animate { transition: transform 0.3s }\n```\n\n### 4. DOM 操作优化\n\n```js\n// ❌ 循环中逐个插入\nlist.forEach(item => parent.appendChild(createEl(item)))\n\n// ✅ 使用 DocumentFragment\nconst frag = document.createDocumentFragment()\nlist.forEach(item => frag.appendChild(createEl(item)))\nparent.appendChild(frag)\n```\n\n---\n\n## 四、网络优化\n\n### 1. CDN 加速\n### 2. HTTP/2 多路复用\n### 3. 接口合并 / BFF\n\n```js\n// ❌ 多个串行请求\nconst a = await fetch('/a')\nconst b = await fetch('/b')\n\n// ✅ 并行 + 合并\nconst [a, b] = await Promise.all([fetch('/a'), fetch('/b')])\n```\n\n### 4. 缓存策略\n\n```yaml\n记忆化/计算结果缓存: Vue computed, React useMemo\n请求缓存: SWR/tanstack-query 的 stale-while-revalidate\nService Worker: 离线缓存(PWA)\n```\n\n---\n\n## 五、打包优化\n\n| 手段 | 目的 |\n|------|------|\n| 代码分割(Code Splitting) | 按路由拆分,减小首屏 JS |\n| 第三方库按需引入 | `import { pick } from 'lodash-es'` 而非 `import _ from 'lodash'` |\n| externals / CDN | React/Vue 等用 CDN 外链,不进 bundle |\n| 分析工具 | webpack-bundle-analyzer / rollup-plugin-visualizer |\n\n---\n\n## 六、Vue 性能优化\n\n```vue\n\n
  • {{ item.name }}
  • \n\n\n\n\n\n \n \n```\n\n---\n\n## 七、高频面试题\n\n### Q1:如何优化首屏加载?\n> 路由懒加载 + 图片懒加载 + 非关键 CSS/JS 延迟 + 首屏内容 SSR/预渲染 + CDN + Gzip。\n\n### Q2:如何处理大列表渲染?\n> 虚拟列表(只渲染可视区域)、分页加载、`requestAnimationFrame` 分片渲染。\n\n### Q3:webpack 代码分割怎么做?\n> `import()` 动态导入,配合 `splitChunks` 提取公共模块,`entry` 拆分多入口。\n\n### Q4:Tree Shaking 的前提是什么?\n> 必须是 **ESM**(ES Module)语法。CommonJS(require)无法被 Tree Shaking(静态分析不了)。\n\n---\n\n*整理时间:2026-06-03*\n", "12-安全与跨域": "# Web 安全 & 跨域面试笔记\n\n---\n\n## 一、XSS(跨站脚本攻击)\n\n### 原理\n\n攻击者往页面**注入恶意脚本**,用户浏览时脚本执行,窃取数据/篡改页面。\n\n### 三种类型\n\n| 类型 | 攻击方式 | 常见来源 |\n|------|----------|----------|\n| 存储型 | 恶意代码存到数据库,所有访问者中招 | 评论、留言 |\n| 反射型 | 恶意代码通过 URL 参数反射回页面 | 搜索、跳转 |\n| DOM 型 | 前端 JS 直接操作 DOM 产生 | innerHTML 注入 |\n\n### 防御\n\n```js\n// 1. 输出编码 / 转义\nfunction escapeHtml(str) {\n return str\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n}\n\n// 2. 避免 innerHTML / document.write\nel.textContent = userInput // ✅ 安全\nel.innerHTML = userInput // ❌ 危险\n\n// 3. CSP(内容安全策略)\n// Content-Security-Policy: default-src 'self'; script-src 'self'\n\n// 4. HttpOnly Cookie(JS 读不到)\n// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Lax\n\n// 5. 输入校验 & 白名单\n```\n\n---\n\n## 二、CSRF(跨站请求伪造)\n\n### 原理\n\n用户登录 A 网站后,被诱骗访问恶意网站 B。B 利用用户在 A 的已登录 Cookie,伪造请求到 A。\n\n```\n用户已登录 bank.com\n ↓\n访问 attacker.com(或点击钓鱼链接)\n ↓\nattacker.com 偷偷提交
    到 bank.com/transfer\n ↓\n浏览器自动附带 bank.com 的 Cookie → 转账成功 💀\n```\n\n### 防御\n\n```yaml\nToken 校验: 每次请求携带服务端生成的 CSRF Token\nSameSite Cookie: Set-Cookie: SameSite=Strict(跨站不发送 Cookie)\nReferer/Origin 校验: 检查请求来源\n双重 Cookie: 请求头自定义字段(非简单请求触发 CORS)\n验证码/二次确认: 敏感操作增加验证\n```\n\n---\n\n## 三、跨域 & CORS\n\n### 什么是跨域?\n\n**协议、域名、端口**任一不同 → 跨域(浏览器同源策略限制)。\n\n### CORS(跨域资源共享)\n\n```yaml\n# 服务端响应头\nAccess-Control-Allow-Origin: https://example.com # 允许的源\nAccess-Control-Allow-Methods: GET, POST, PUT # 允许的方法\nAccess-Control-Allow-Headers: Content-Type # 允许的自定义头\nAccess-Control-Allow-Credentials: true # 允许携带 Cookie\n```\n\n### 简单请求 vs 预检请求\n\n| 类型 | 条件 | 过程 |\n|------|------|------|\n| 简单请求 | GET/POST/HEAD + 标准头 | 直接发 → 检查响应头 |\n| 预检请求 | 其他方法/自定义头/Content-Type 非标准 | 先 OPTIONS → 200 → 再发正式请求 |\n\n### 其他跨域方案\n\n```yaml\nJSONP: \n```\n\n---\n\n## 四、计算属性\n\n```jsx\n// React:useMemo\nconst double = useMemo(() => count * 2, [count])\n\n// Vue:computed(自动依赖追踪,更简洁)\nconst double = computed(() => count.value * 2)\n```\n\n---\n\n## 五、条件/列表渲染\n\n```jsx\n// React:JS 表达式\n{show &&
    shown
    }\n{items.map(item => )}\n\n// Vue:指令\n
    shown
    \n
    {{ item.name }}
    \n```\n\n---\n\n## 六、组件通信\n\n| 场景 | React | Vue |\n|------|-------|-----|\n| 父→子 | props | props / `defineProps` |\n| 子→父 | 回调函数 | `$emit` / `defineEmits` |\n| 跨层级 | Context | provide / inject |\n| 全局 | Redux/Zustand | Pinia / Vuex |\n| 双向绑定 | 手动:value + onChange | `v-model` |\n| 插槽 | children / render props | `` / 具名插槽 |\n\n---\n\n## 七、状态管理\n\n| 对比 | React 生态 | Vue 生态 |\n|------|-----------|----------|\n| 主流 | Redux / Zustand / Jotai | Pinia(官方) |\n| 特点 | 需中间件处理异步 | 原生支持 action 异步 |\n| 代码量 | 较多(样板代码) | 较少 |\n| DevTools | Redux DevTools | Vue DevTools |\n\n---\n\n## 八、生命周期对比\n\n| React(class) | React(hooks) | Vue(Options) |\n|---------------|----------------|----------------|\n| componentDidMount | useEffect(fn, []) | mounted |\n| componentDidUpdate | useEffect(fn, [deps]) | updated |\n| componentWillUnmount | useEffect(() => fn, []) | beforeUnmount |\n| shouldComponentUpdate | React.memo | computed(自动) |\n\n---\n\n## 九、性能对比\n\n| 维度 | React | Vue |\n|------|-------|-----|\n| 更新粒度 | 组件级 | 组件级 + 精确到变量 |\n| 优化手段 | useMemo/useCallback/memo | 自动追踪,较少手写优化 |\n| 虚拟 DOM | 全量 diff | 编译时优化(静态提升、补丁标记) |\n| 首屏体积 | ~40KB(react + react-dom) | ~16KB(runtime-only) |\n\n---\n\n## 十、生态\n\n| 领域 | React | Vue |\n|------|-------|-----|\n| SSR/SSG | Next.js | Nuxt |\n| 移动端 | React Native | uni-app / taro |\n| 桌面端 | Electron + React | Electron + Vue |\n| 组件库 | Ant Design、MUI | Element Plus、Ant Design Vue |\n| 类型支持 | TS 原生友好 | Vue 3 + TS 较好 |\n\n---\n\n## 十一、高频面试题\n\n### Q1:React 和 Vue 最大的区别?\n> React 是**不可变数据 + 手动 setState**,Vue 是**响应式数据 + 自动追踪**。React 偏函数式、需显式优化;Vue 偏声明式、编译器帮做优化。\n\n### Q2:Vue 的 v-model 在 React 中怎么实现?\n> React 没有语法糖,需手动传 `value` + `onChange`,或用第三方库。本质是受控组件的模式。\n\n### Q3:React 的 useEffect 和 Vue 的 watch 有什么区别?\n> useEffect 是**副作用钩子**(渲染后执行),需手动声明依赖;Vue 的 watch 是**声明式监听**,依赖自动追踪。computed 更接近 useMemo。\n\n### Q4:为什么大型项目选 React 更多?\n> 生态更丰富(Next.js + TypeScript 深度整合)、函数式编程利于抽象与测试、React Native 跨端能力。但实际上两者都可以支撑大型项目。\n\n### Q5:Vue 3 相比 Vue 2 最大的改进?\n> Composition API(逻辑复用)、Proxy 响应式、更好的 TS 支持、Tree-shaking 减肥。\n\n---\n\n*整理时间:2026-06-03*\n", "17-Vue进阶": "# Vue 进阶面试笔记\n> 基础响应式原理见 `05-Vue响应式.md`\n\n---\n\n## 一、Composition API 深入\n\n### setup vs Options API\n\n```vue\n\n\n\n\n\n```\n\n### 为什么引入 Composition API?\n\n| 问题 | Options API | Composition API |\n|------|-------------|-----------------|\n| 逻辑复用 | Mixin(命名冲突/来源不清) | 自定义 Hook(composables) |\n| 代码组织 | 按选项拆分(data/methods/watch 割裂) | 按功能聚合(相关代码在一起) |\n| TS 支持 | 差 | 好 |\n\n### composables(自定义 Hook)\n\n```js\n// useMouse.js\nexport function useMouse() {\n const x = ref(0)\n const y = ref(0)\n function update(e) { x.value = e.pageX; y.value = e.pageY }\n onMounted(() => window.addEventListener('mousemove', update))\n onUnmounted(() => window.removeEventListener('mousemove', update))\n return { x, y }\n}\n\n// 使用\nconst { x, y } = useMouse()\n```\n\n---\n\n## 二、组件通信大全\n\n### 1. props / emits\n\n```vue\n\n\n\n\n\n```\n\n### 2. provide / inject\n\n```js\n// 祖先\nprovide('theme', 'dark')\n\n// 后代\nconst theme = inject('theme')\n```\n\n### 3. v-model 双向绑定\n\n```vue\n\n\n\n\n\n\n\n\n```\n\n### 4. ref + defineExpose\n\n```vue\n\n\n\n\n\n// childRef.value.reset()\n```\n\n### 5. 事件总线(不推荐)\n\n小型项目可用 `mitt` 库做简单的事件总线。\n\n---\n\n## 三、插槽(Slot)\n\n```vue\n\n Hello \n\n\n\n\n \n \n \n\n\n\n\n {{ item }} \n\n```\n\n---\n\n## 四、Vue Router\n\n```js\nconst routes = [\n {\n path: '/user/:id',\n component: () => import('@/views/User.vue'), // 懒加载\n meta: { requiresAuth: true },\n children: [/* 嵌套路由 */]\n }\n]\n\n// 导航守卫\nrouter.beforeEach((to, from) => {\n if (to.meta.requiresAuth && !token) return '/login'\n})\n```\n\n| 对比 | hash | history |\n|------|------|---------|\n| URL | `/#/page` | `/page` |\n| 原理 | hashchange 事件 | History API (pushState) |\n| 部署 | 无需服务端配置 | 需服务端支持(404 回退到 index.html) |\n\n---\n\n## 五、Pinia\n\n```js\n// 定义 Store\nexport const useUserStore = defineStore('user', () => {\n const user = ref(null)\n const isLogin = computed(() => !!user.value)\n\n async function login(credentials) {\n user.value = await api.login(credentials)\n }\n\n return { user, isLogin, login }\n})\n\n// 使用\nconst store = useUserStore()\nstore.login({ name, password })\n```\n\n### Pinia vs Vuex\n\n| 对比 | Pinia | Vuex |\n|------|-------|------|\n| 体积 | ~1KB | 较大 |\n| TS 支持 | 原生 | 需额外类型声明 |\n| Mutation | 无(直接改 state) | 有 |\n| 模块 | 多个独立 store | modules 嵌套 |\n| 官方 | Vue 3 官方推荐 | Vue 2 官方 |\n\n---\n\n## 六、KeepAlive\n\n```vue\n\n\n \n\n\n\n\n```\n\n---\n\n## 七、nextTick\n\n```js\n// 数据变更后,DOM 还没更新\ncount.value++\nconsole.log(document.getElementById('num').textContent) // 旧值\n\nawait nextTick()\n// DOM 已更新\nconsole.log(document.getElementById('num').textContent) // 新值\n```\n\n> Vue 的 DOM 更新是**异步批处理**,同一事件循环内的多次数据变更只触发一次更新。\n\n---\n\n## 八、高频面试题\n\n### Q1:Mixin 有什么问题?Composition API 如何解决?\n> Mixin:命名冲突、来源不明、隐式依赖。Composition API:显式引入、函数作用域隔离、TS 友好。\n\n### Q2:v-if 和 v-show 的区别?\n> `v-if` 控制 DOM 的**存在**(真正的条件渲染),切换开销大;`v-show` 控制 `display`(始终挂载),初始渲染开销大。频繁切换用 v-show。\n\n### Q3:v-for 为什么必须加 key?\n> key 是虚拟 DOM 的唯一标识,帮助 diff 算法高效复用/移动 DOM 节点。不用 index 作 key 因为列表顺序变化时 index 不稳定。\n\n### Q4:Vue 组件 data 为什么必须是函数?\n> 返回对象的引用会被所有实例共享。函数返回新对象,每个实例独立一份数据。\n\n### Q5:watch 和 computed 的选择?\n> computed:依赖变化时**返回**新值,有缓存,不执行副作用。watch:数据变化时**执行**操作(API 请求、DOM 操作等副作用)。\n\n---\n\n*整理时间:2026-06-03*\n"}; let currentId = null; let sidebarOpen = false; // 渲染侧边栏 function renderList(filter = '') { const list = document.getElementById('note-list'); const filtered = NOTES.filter(n => n.title.toLowerCase().includes(filter.toLowerCase()) || n.id.includes(filter) ); list.innerHTML = filtered.map(n => ` ${n.title} `).join(''); } // 加载笔记内容(内嵌版,无需 fetch) function loadNote(id) { currentId = id; const note = NOTES.find(n => n.id === id); if (!note || !NOTES_CONTENT[id]) return; document.getElementById('topbar-title').textContent = note.title; const html = renderMarkdown(NOTES_CONTENT[id]); document.getElementById('content').innerHTML = `

    ${note.title}

    整理时间:${note.date}
    ${html}
    `; document.querySelectorAll('.sidebar-item').forEach(el => { el.classList.toggle('active', el.getAttribute('href') === '#' + id); }); if (window.innerWidth <= 768) toggleSidebar(false); window.scrollTo(0, 0); } // 简单 Markdown 渲染器 function renderMarkdown(md) { let html = md; // 代码块 html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => '
    ' + escapeHtml(code.trim()) + '
    ' ); // 行内代码 html = html.replace(/`([^`]+)`/g, '$1'); // 标题 html = html.replace(/^#### (.+)$/gm, '

    $1

    '); html = html.replace(/^### (.+)$/gm, '

    $1

    '); html = html.replace(/^## (.+)$/gm, '

    $1

    '); html = html.replace(/^# (.+)$/gm, '

    $1

    '); // 粗体 html = html.replace(/\*\*(.+?)\*\*/g, '$1'); // 表格 html = html.replace(/\|(.+)\|\n\|[-| :]+\|\n([\s\S]*?)(?=\n\n|\n###|\n##|\n#|$)/g, (match) => { const lines = match.trim().split('\n'); if (lines.length < 2) return match; const headers = lines[0].split('|').filter(c => c.trim()).map(c => `${c.trim()}`).join(''); const rows = lines.slice(2).map(line => { const cells = line.split('|').filter(c => c.trim()).map(c => `${c.trim()}`).join(''); return `${cells}`; }).join(''); return `${headers}${rows}
    `; }); // 列表 html = html.replace(/^[\s]*[-*] (.+)$/gm, '
  • $1
  • '); html = html.replace(/(
  • .*<\/li>)/s, '
      $1
    '); // 引用 html = html.replace(/^> (.+)$/gm, '
    $1
    '); // 分隔线 html = html.replace(/^---$/gm, '
    '); // 段落 html = html.replace(/^(?!<[h|ul|ol|pre|table|blockquote|hr|div)(.+)$/gm, '

    $1

    '); return html; } function escapeHtml(str) { return str.replace(/&/g,'&').replace(//g,'>'); } function filterNotes(val) { renderList(val); } function toggleSidebar(force) { sidebarOpen = force !== undefined ? force : !sidebarOpen; document.getElementById('sidebar').classList.toggle('open', sidebarOpen); document.getElementById('overlay').classList.toggle('show', sidebarOpen); } // 路由处理 function init() { renderList(); const hash = location.hash.slice(1); if (hash) { loadNote(hash); } } window.addEventListener('hashchange', () => { const hash = location.hash.slice(1); if (hash) loadNote(hash); }); init();